# This file is part of Email-Reminder.
#
# Email-Reminder is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# Email-Reminder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Email-Reminder; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

package EmailReminder::EventList;

# Holds all user information and events.
#
# Events are stored in the proper EventStore and they can be accessed
# using this class.  The main XML parsing and generation happen here.

use strict;

use Email::Valid;
use XML::DOM;

use EmailReminder::AnniversaryEvent;
use EmailReminder::AnniversaryStore;
use EmailReminder::BirthdayEvent;
use EmailReminder::BirthdayStore;
use EmailReminder::MonthlyEvent;
use EmailReminder::MonthlyStore;
use EmailReminder::Utils;
use EmailReminder::WeeklyEvent;
use EmailReminder::WeeklyStore;
use EmailReminder::YearlyEvent;
use EmailReminder::YearlyStore;

# XML tags, attributes and values

my $EMAIL_TAG       = 'email';
my $EVENT_TAG       = 'event';
my $EVENTS_TAG      = 'events';
my $FIRST_NAME_TAG  = 'first_name';
my $LAST_NAME_TAG   = 'last_name';
my $USER_TAG        = 'email-reminder_user';

my $TYPE_ATTR       = 'type';

sub new
{
    my ($class, $filename, $create) = @_;
    
    my $this = { "NEXT_EVENT_ID" => 0,
                 "LOADED_FILENAME" => $filename,
                 "XML_DOC" => undef,
                 "EVENTS_NODE" => undef,
                 "STORES" => {},
             };

    bless $this, $class;
    
    $this->process_file($create);
    
    return $this;
}

sub process_file
{
    my ($this, $create, $readonly) = @_;

    # Make sure the config file exists and is readable
    my $filename = $this->{LOADED_FILENAME};
    unless (-e $filename)
    {
        if ($create)
        {
            open CFG, '>:utf8', "$filename";
            print CFG '<?xml version="1.0"?><email-reminder_user/>';
            close CFG;
        }
        else
        {
            warn "File '$filename' does not exist and it is impossible to create it.\n";
            exit(1);
        }
    }
    unless (-r $filename)
    {
        warn "File '$filename' exists but is not readable.\n";
        exit(1);
    }
    if (!$readonly and !(-w $filename))
    {
        warn "WARNING: File '$filename' is not writable, your changes will be lost!\n";
    }

    # Start parsing the XML file
    my $parser = XML::DOM::Parser->new();
    my $doc;
    eval {
        $doc = $parser->parsefile($filename);
    };
    unless (defined($doc))
    {
	warn "File '$filename' is an invalid XML file. Fix it or delete it.\n";
	exit(1);
    }
    $this->{XML_DOC} = $doc;

    # Read user info
    my $user = $doc->getElementsByTagName($USER_TAG)->item(0);   
    unless (defined($user))
    {
	warn "File '$filename' is an invalid XML file. Fix it or delete it.\n";
	exit(1);
    }
    $this->{USER_NODE} = $user;

    # Read events
    my $events = $doc->getElementsByTagName($EVENTS_TAG)->item(0);
    return unless defined($events);
    $this->{EVENTS_NODE} = $events;

    foreach my $event_node ($events->getElementsByTagName($EVENT_TAG)){
	my $type = $event_node->getAttribute($TYPE_ATTR);
	my $event = $this->create_event($type, $event_node);
        next unless defined($event);

        # Add to proper EventStore
        my $store = $this->get_model($type);
        $store->add_event($event);
    }
}

sub create_event
{
    my ($this, $type, $event_node) = @_;

    if (!defined($event_node))
    {
        $event_node = $this->{XML_DOC}->createElement($EVENT_TAG);
        $event_node->setAttribute($TYPE_ATTR, $type);
        
        my $events = $this->{EVENTS_NODE};
        unless (defined($events))
        {
            $events = $this->{XML_DOC}->createElement($EVENTS_TAG);
            $this->{USER_NODE}->appendChild($events);
            $this->{EVENTS_NODE} = $events;
        }
        $events->appendChild($event_node);
    }

    my $event;
    my $id = $this->{NEXT_EVENT_ID}++;

    if ($type eq EmailReminder::BirthdayEvent->get_type()) {
        $event = EmailReminder::BirthdayEvent->new($event_node, $id);
    } 
    elsif ($type eq EmailReminder::AnniversaryEvent->get_type()) {
        $event = EmailReminder::AnniversaryEvent->new($event_node, $id);
    } 
    elsif ($type eq EmailReminder::MonthlyEvent->get_type()) {
        $event = EmailReminder::MonthlyEvent->new($event_node, $id);
    }
    elsif ($type eq EmailReminder::WeeklyEvent->get_type()) {
        $event = EmailReminder::WeeklyEvent->new($event_node, $id);
    }
    elsif ($type eq EmailReminder::YearlyEvent->get_type()) {
        $event = EmailReminder::YearlyEvent->new($event_node, $id);
    }

    return $event;
}

sub save
{
    my $this = shift;
    my $verbose = shift;
    my $filename = shift || $this->{LOADED_FILENAME};

    my $xml_document = $this->{XML_DOC}->toString();
    print $xml_document if $verbose;

    # Overwrite the file
    if (open OUT, '>:utf8', "$filename")
    {
        print OUT $xml_document;
        print "Sucessfully wrote reminders to '$filename'\n" if $verbose;
        close OUT;
    }
    else
    {
        print STDERR "Cannot write to file '$filename', your changes have been lost.\n";
    }
}

sub get_model
{
    my ($this, $type) = @_;

    my $store = $this->{STORES}->{$type};
    unless (defined($store)) {
        if ($type eq EmailReminder::AnniversaryEvent->get_type()) {
            $store = EmailReminder::AnniversaryStore->new();
        } 
        elsif ($type eq EmailReminder::BirthdayEvent->get_type()) {
            $store = EmailReminder::BirthdayStore->new();
        } 
        elsif ($type eq EmailReminder::MonthlyEvent->get_type()) {
            $store = EmailReminder::MonthlyStore->new();
        }
        elsif ($type eq EmailReminder::WeeklyEvent->get_type()) {
            $store = EmailReminder::WeeklyStore->new();
        }
        elsif ($type eq EmailReminder::YearlyEvent->get_type()) {
            $store = EmailReminder::YearlyStore->new();
        }

        $store->init();
        $this->{STORES}->{$type} = $store;
    }

    return $store;
}

sub get_events
{
    my $this = shift;

    my @events = ();
    foreach my $store (values(%{$this->{STORES}})) {
        push(@events, @{$store->get_events()});
    }

    return @events;
}

sub add_event
{
    my ($this, $event_type) = @_;
    my $event = $this->create_event($event_type);
    return 0 unless defined($event);

    $event->set_name("<New Event>");
    $event->set_reminders([0]); # default reminder: same day

    return $this->{STORES}->{$event_type}->add_event($event);
}

# View/edit user properties

sub get_user_name
{
    my $this = shift;

    my $fname = EmailReminder::Utils::get_node_value($this->{USER_NODE}, $FIRST_NAME_TAG) || '';
    my $lname = EmailReminder::Utils::get_node_value($this->{USER_NODE}, $LAST_NAME_TAG) || '';

    if (!$fname and !$lname) {
        # Get name from UNIX password file
        my @pwinfo = getpwuid($>);
        my $fullname = $pwinfo[6]; 
        $fullname =~ s/[^0-9A-Za-z_\- ]//g;
        my @name_parts = split(/ /, $fullname);
        
        $fname = $name_parts[0];
        $lname = $name_parts[-1] if @name_parts > 1;
    } elsif (!$fname) {
        $fname = $lname;
        $lname = '';
    }

    return ($fname, $lname);
}

sub get_user_email
{
    my $this = shift;
    return EmailReminder::Utils::get_node_value($this->{USER_NODE}, $EMAIL_TAG) || "";
}

sub set_user_fname
{
    my ($this, $new_fname) = @_;
    return EmailReminder::Utils::set_node_value($this->{USER_NODE}, $FIRST_NAME_TAG, $new_fname);
}

sub set_user_lname
{
    my ($this, $new_lname) = @_;
    return EmailReminder::Utils::set_node_value($this->{USER_NODE}, $LAST_NAME_TAG, $new_lname);
}

# Return 0 if the email was ignored (invalid)
sub set_user_email
{
    my ($this, $new_email) = @_;
    if (!$new_email or Email::Valid->address($new_email)) {
        return EmailReminder::Utils::set_node_value($this->{USER_NODE}, $EMAIL_TAG, $new_email);        
    } else {
        return 0;
    }
}

1;
